{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pythreejs import *\n",
    "import numpy as np\n",
    "from IPython.display import display\n",
    "from ipywidgets import HTML, Text, Output, VBox\n",
    "from traitlets import link, dlink"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Simple sphere and text"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ball = Mesh(geometry=SphereGeometry(radius=1, widthSegments=32, heightSegments=24), \n",
    "            material=MeshLambertMaterial(color='red'),\n",
    "            position=[2, 1, 0])\n",
    "\n",
    "c = PerspectiveCamera(position=[0, 5, 5], up=[0, 1, 0],\n",
    "                      children=[DirectionalLight(color='white', position=[3, 5, 1], intensity=0.5)])\n",
    "\n",
    "scene = Scene(children=[ball, c, AmbientLight(color='#777777')])\n",
    "\n",
    "renderer = Renderer(camera=c, \n",
    "                    scene=scene, \n",
    "                    controls=[OrbitControls(controlling=c)])\n",
    "display(renderer)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ball.scale = (0.5,) * 3"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import time, math\n",
    "ball.material.color = '#4400dd'\n",
    "for i in range(1, 150, 2):\n",
    "    ball.scale = (i / 100.,) * 3\n",
    "    ball.position = [math.cos(i / 10.), math.sin(i / 50.), i / 100.]\n",
    "    time.sleep(.05)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Clickable Surface\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Generate surface data:\n",
    "view_width = 600\n",
    "view_height = 400\n",
    "nx, ny = (20, 20)\n",
    "xmax=1\n",
    "x = np.linspace(-xmax, xmax, nx)\n",
    "y = np.linspace(-xmax, xmax, ny)\n",
    "xx, yy = np.meshgrid(x, y)\n",
    "z = xx ** 2 - yy ** 2\n",
    "#z[6,1] = float('nan')\n",
    "\n",
    "\n",
    "# Generate scene objects from data:\n",
    "surf_g = SurfaceGeometry(z=list(z[::-1].flat), \n",
    "                         width=2 * xmax,\n",
    "                         height=2 * xmax,\n",
    "                         width_segments=nx - 1,\n",
    "                         height_segments=ny - 1)\n",
    "\n",
    "surf = Mesh(geometry=surf_g,\n",
    "            material=MeshLambertMaterial(map=height_texture(z[::-1], 'YlGnBu_r')))\n",
    "\n",
    "surfgrid = SurfaceGrid(geometry=surf_g, material=LineBasicMaterial(color='black'),\n",
    "                       position=[0, 0, 1e-2])  # Avoid overlap by lifting grid slightly\n",
    "\n",
    "# Set up picking bojects:\n",
    "hover_point = Mesh(geometry=SphereGeometry(radius=0.05),\n",
    "                   material=MeshLambertMaterial(color='hotpink'))\n",
    "\n",
    "click_picker = Picker(controlling=surf, event='dblclick')\n",
    "hover_picker = Picker(controlling=surf, event='mousemove')\n",
    "\n",
    "# Set up scene:\n",
    "key_light = DirectionalLight(color='white', position=[3, 5, 1], intensity=0.4)\n",
    "c = PerspectiveCamera(position=[0, 3, 3], up=[0, 0, 1], aspect=view_width / view_height,\n",
    "                      children=[key_light])\n",
    "\n",
    "scene = Scene(children=[surf, c, surfgrid, hover_point, AmbientLight(intensity=0.8)])\n",
    "\n",
    "renderer = Renderer(camera=c, scene=scene,\n",
    "                    width=view_width, height=view_height,\n",
    "                    controls=[OrbitControls(controlling=c), click_picker, hover_picker])\n",
    "\n",
    "\n",
    "# Set up picking responses:\n",
    "# Add a new marker when double-clicking:\n",
    "out = Output()\n",
    "def f(change):\n",
    "    value = change['new']\n",
    "    with out:\n",
    "        print('Clicked on %s' % (value,))\n",
    "    point = Mesh(geometry=SphereGeometry(radius=0.05), \n",
    "                 material=MeshLambertMaterial(color='red'),\n",
    "                 position=value)\n",
    "    scene.add(point)\n",
    "\n",
    "click_picker.observe(f, names=['point'])\n",
    "\n",
    "# Have marker follow picker point:\n",
    "link((hover_point, 'position'), (hover_picker, 'point'))\n",
    "\n",
    "# Show picker point coordinates as a label:\n",
    "h = HTML()\n",
    "def g(change):\n",
    "    h.value = 'Green point at (%.3f, %.3f, %.3f)' % tuple(change['new'])\n",
    "g({'new': hover_point.position})\n",
    "hover_picker.observe(g, names=['point'])\n",
    "\n",
    "display(VBox([h, renderer, out]))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "surf_g.z = list((-z[::-1]).flat)\n",
    "surf.material.map = height_texture(-z[::-1])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Design our own texture"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "from scipy import ndimage\n",
    "import matplotlib\n",
    "import matplotlib.pyplot as plt\n",
    "from skimage import img_as_ubyte \n",
    "\n",
    "jet = matplotlib.cm.get_cmap('jet')\n",
    "\n",
    "np.random.seed(int(1)) # start random number generator\n",
    "n = int(5) # starting points\n",
    "size = int(32) # size of image\n",
    "im = np.zeros((size,size)) # create zero image\n",
    "points = size*np.random.random((2, n**2)) # locations of seed values\n",
    "im[(points[0]).astype(np.int), (points[1]).astype(np.int)] = size # seed high values\n",
    "im = ndimage.gaussian_filter(im, sigma=size/(float(4)*n)) # smooth high values into surrounding areas\n",
    "im *= 1/np.max(im)# rescale to be in the range [0,1]\n",
    "rgba_im = img_as_ubyte(jet(im)) # convert the values to rgba image using the jet colormap\n",
    "\n",
    "t = DataTexture(data=rgba_im, format='RGBAFormat', width=size, height=size)\n",
    "\n",
    "geometry = SphereGeometry(radius=1, widthSegments=16, heightSegments=10)#TorusKnotGeometry(radius=2, radialSegments=200)\n",
    "material = MeshLambertMaterial(map=t)\n",
    "\n",
    "myobject = Mesh(geometry=geometry, material=material)\n",
    "c = PerspectiveCamera(position=[0, 3, 3], fov=40,\n",
    "                      children=[DirectionalLight(color='#ffffff', position=[3, 5, 1], intensity=0.5)])\n",
    "scene = Scene(children=[myobject, c, AmbientLight(color='#777777')])\n",
    "\n",
    "renderer = Renderer(camera=c, scene = scene, controls=[OrbitControls(controlling=c)], width=400, height=400)\n",
    "display(renderer)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Lines"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# On windows, linewidth of the material has no effect\n",
    "size = 4\n",
    "linesgeom = Geometry(vertices=[[0, 0, 0],\n",
    "                                    [size, 0, 0],\n",
    "                                    [0, 0, 0],\n",
    "                                    [0, size, 0],\n",
    "                                    [0, 0, 0],\n",
    "                                    [0, 0, size]],\n",
    "                          colors = ['red', 'red', 'green', 'green', 'white', 'orange'])\n",
    "lines = Line(geometry=linesgeom, \n",
    "             material=LineBasicMaterial(linewidth=5, vertexColors='VertexColors'), \n",
    "             type='LinePieces',\n",
    "            )\n",
    "scene = Scene(children=[\n",
    "    lines,\n",
    "    DirectionalLight(color='#ccaabb', position=[0,10,0]),\n",
    "    AmbientLight(color='#cccccc'),\n",
    "    ])\n",
    "c = PerspectiveCamera(position=[10, 10, 10])\n",
    "renderer = Renderer(camera=c, background='black', background_opacity=1, scene=scene, controls=[OrbitControls(controlling=c)],\n",
    "                    width=400, height=400)\n",
    "display(renderer)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Parametric Functions\n",
    "\n",
    "\n",
    "To use the ParametricGeometry class, you need to specify a javascript function as a string. The function should take two parameters that vary between 0 and 1, and a `THREE.Vector3(x,y,z)` that should be modified in place.\n",
    "\n",
    "If you want to build the surface in Python, you'll need to explicitly construct the vertices and faces and build a basic geometry from the vertices and faces."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "f = \"\"\"\n",
    "function f(origu, origv, out) {\n",
    "    // scale u and v to the ranges I want: [0, 2*pi]\n",
    "    var u = 2*Math.PI*origu;\n",
    "    var v = 2*Math.PI*origv;\n",
    "    \n",
    "    var x = Math.sin(u);\n",
    "    var y = Math.cos(v);\n",
    "    var z = Math.cos(u+v);\n",
    "    \n",
    "    out.set(x,y,z)\n",
    "}\n",
    "\"\"\"\n",
    "surf_g = ParametricGeometry(func=f, slices=16, stacks=16);\n",
    "\n",
    "surf = Mesh(geometry=surf_g, material=MeshLambertMaterial(color='green', side='FrontSide'))\n",
    "surf2 = Mesh(geometry=surf_g, material=MeshLambertMaterial(color='yellow', side='BackSide'))\n",
    "c = PerspectiveCamera(position=[5, 5, 3], up=[0, 0, 1],\n",
    "                      children=[DirectionalLight(color='white',\n",
    "                                                 position=[3, 5, 1],\n",
    "                                                 intensity=0.6)])\n",
    "scene = Scene(children=[surf, surf2, c, AmbientLight(intensity=0.5)])\n",
    "renderer = Renderer(camera=c, scene=scene, controls=[OrbitControls(controlling=c)], width=400, height=400)\n",
    "display(renderer)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Indexed Geometries\n",
    "\n",
    "The PlainGeometry lets you specify vertices and faces for a surface."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pythreejs import *\n",
    "from IPython.display import display\n",
    "\n",
    "vertices = [\n",
    "    [0, 0, 0],\n",
    "    [0, 0, 1],\n",
    "    [0, 1, 0],\n",
    "    [0, 1, 1],\n",
    "    [1, 0, 0],\n",
    "    [1, 0, 1],\n",
    "    [1, 1, 0],\n",
    "    [1, 1, 1]\n",
    "]\n",
    "\n",
    "faces = [\n",
    "    [0, 1, 3],\n",
    "    [0, 3, 2],\n",
    "    [0, 2, 4],\n",
    "    [2, 6, 4],\n",
    "    [0, 4, 1],\n",
    "    [1, 4, 5],\n",
    "    [2, 3, 6],\n",
    "    [3, 7, 6],\n",
    "    [1, 5, 3],\n",
    "    [3, 5, 7],\n",
    "    [4, 6, 5],\n",
    "    [5, 6, 7]\n",
    "]\n",
    "\n",
    "vertexcolors = ['#000000', '#0000ff', '#00ff00', '#ff0000',\n",
    "                '#00ffff', '#ff00ff', '#ffff00', '#ffffff']\n",
    "\n",
    "# Map the vertex colors into the 'color' slot of the faces\n",
    "faces = [f + [None, [vertexcolors[i] for i in f], None] for f in faces]\n",
    "\n",
    "# Create the geometry:\n",
    "cubeGeometry = Geometry(vertices=vertices,\n",
    "    faces=faces,\n",
    "    colors=vertexcolors)\n",
    "# Calculate normals per face, for nice crisp edges:\n",
    "cubeGeometry.exec_three_obj_method('computeFaceNormals')\n",
    "\n",
    "# Create a mesh. Note that the material need to be told to use the vertex colors.\n",
    "myobjectCube = Mesh(\n",
    "    geometry=cubeGeometry,\n",
    "    material=MeshLambertMaterial(vertexColors='VertexColors'),\n",
    "    position=[-0.5, -0.5, -0.5],   # Center the cube\n",
    ")\n",
    "\n",
    "# Set up a scene and render it:\n",
    "cCube = PerspectiveCamera(position=[3, 3, 3], fov=20,\n",
    "                      children=[DirectionalLight(color='#ffffff', position=[-3, 5, 1], intensity=0.5)])\n",
    "sceneCube = Scene(children=[myobjectCube, cCube, AmbientLight(color='#dddddd')])\n",
    "\n",
    "rendererCube = Renderer(camera=cCube, background='black', background_opacity=1,\n",
    "                        scene=sceneCube, controls=[OrbitControls(controlling=cCube)])\n",
    "\n",
    "display(rendererCube)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Buffer Geometries\n",
    "\n",
    "The PlainBufferGeometry object uses several tricks to speed up both the transfer of data and the rendering of the data."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pythreejs import *\n",
    "import numpy as np\n",
    "from IPython.display import display\n",
    "\n",
    "vertices = np.asarray([\n",
    "    [0, 0, 0],\n",
    "    [0, 0, 1],\n",
    "    [0, 1, 0],\n",
    "    [0, 1, 1],\n",
    "    [1, 0, 0],\n",
    "    [1, 0, 1],\n",
    "    [1, 1, 0],\n",
    "    [1, 1, 1]\n",
    "], dtype='float32')\n",
    "\n",
    "faces = np.asarray([\n",
    "    [0, 1, 3],\n",
    "    [0, 3, 2],\n",
    "    [0, 2, 4],\n",
    "    [2, 6, 4],\n",
    "    [0, 4, 1],\n",
    "    [1, 4, 5],\n",
    "    [2, 3, 6],\n",
    "    [3, 7, 6],\n",
    "    [1, 5, 3],\n",
    "    [3, 5, 7],\n",
    "    [4, 6, 5],\n",
    "    [5, 6, 7]\n",
    "], dtype='uint16').ravel()  # We need to flatten index array\n",
    "\n",
    "\n",
    "vertexcolors = np.asarray([(0,0,0), (0,0,1), (0,1,0), (1,0,0),\n",
    "                           (0,1,1), (1,0,1), (1,1,0), (1,1,1)], dtype='float32')\n",
    "\n",
    "cubeGeometry = BufferGeometry(attributes=dict(\n",
    "    position=BufferAttribute(vertices, normalized=False),\n",
    "    index=BufferAttribute(faces, normalized=False),\n",
    "    color=BufferAttribute(vertexcolors),\n",
    "))\n",
    "\n",
    "myobjectCube = Mesh(\n",
    "    geometry=cubeGeometry,\n",
    "    material=MeshLambertMaterial(vertexColors='VertexColors'),\n",
    "    position=[-0.5, -0.5, -0.5]   # Center the cube\n",
    ")\n",
    "cCube = PerspectiveCamera(\n",
    "    position=[3, 3, 3], fov=20,\n",
    "    children=[DirectionalLight(color='#ffffff', position=[-3, 5, 1], intensity=0.5)])\n",
    "sceneCube = Scene(children=[myobjectCube, cCube, AmbientLight(color='#dddddd')])\n",
    "\n",
    "rendererCube = Renderer(camera=cCube, background='black', background_opacity=1,\n",
    "                        scene = sceneCube, controls=[OrbitControls(controlling=cCube)])\n",
    "\n",
    "display(rendererCube)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note that there are no face normals logic for buffer geometries, as the attributes are *vertex* attributes. If you want to add sharp edges for a BufferGeometry, you then have to duplicate the vertices (i.e., don't use an index attribute), and calculate the normals yourself."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Examples to do\n",
    "\n",
    "- image texture (with webcam picture!)\n",
    "- scaled object: point that doesn't change size\n",
    "- vertex shader\n",
    "- switch between phong, lambert, depth, and wireframe materials, normalmaterial"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.5.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
